crypto: add uuidv7 monotonic counter#62601
Conversation
|
Review requested:
|
There was a problem hiding this comment.
Pull request overview
Adds a monotonic counter to crypto.randomUUIDv7() so UUIDv7 values are strictly increasing even when multiple UUIDs are generated within the same millisecond.
Changes:
- Implement monotonic
rand_acounter state for UUIDv7 generation (buffered + unbuffered paths). - Write timestamp + counter into UUIDv7 bytes before serialization.
- Tighten/extend tests to assert strict ordering, including burst generation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| test/parallel/test-crypto-randomuuidv7.js | Updates ordering assertions to require strict monotonic UUID string ordering and adds a burst test. |
| lib/internal/crypto/random.js | Adds UUIDv7-specific buffered state and monotonic timestamp/counter logic used by randomUUIDv7(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| let prev = randomUUIDv7(); | ||
| for (let i = 0; i < 100; i++) { | ||
| const curr = randomUUIDv7(); | ||
| // UUIDs with later timestamps must sort after earlier ones. | ||
| // Within the same millisecond, ordering depends on random bits, | ||
| // so we only assert >= on the timestamp portion. | ||
| const prevTs = parseInt(prev.replace(/-/g, '').slice(0, 12), 16); | ||
| const currTs = parseInt(curr.replace(/-/g, '').slice(0, 12), 16); | ||
| assert(currTs >= prevTs, | ||
| `Timestamp went backwards: ${currTs} < ${prevTs}`); | ||
| // With a monotonic counter in rand_a, each UUID must be strictly greater | ||
| // than the previous regardless of whether the timestamp changed. | ||
| assert(curr > prev, | ||
| `UUID ordering violated: ${curr} <= ${prev}`); | ||
| prev = curr; | ||
| } | ||
| } | ||
|
|
||
| // Sub-millisecond ordering: a tight synchronous burst exercises the counter | ||
| // increment path and must also produce strictly increasing UUIDs. | ||
| { | ||
| const burst = []; | ||
| for (let i = 0; i < 500; i++) { | ||
| burst.push(randomUUIDv7()); | ||
| } | ||
| for (let i = 1; i < burst.length; i++) { | ||
| assert(burst[i] > burst[i - 1], | ||
| `Sub-millisecond ordering violated at index ${i}: ` + | ||
| `${burst[i]} <= ${burst[i - 1]}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
The new monotonic-ordering assertions only exercise the default (buffered) code path. Since this PR changes both buffered and disableEntropyCache: true (unbuffered) implementations, it would be good to add at least one ordering check for randomUUIDv7({ disableEntropyCache: true }) as well, to ensure the unbuffered counter/timestamp logic stays monotonic too.
|
Can you confirm that it’s intentional for |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #62601 +/- ##
==========================================
+ Coverage 89.70% 89.81% +0.10%
==========================================
Files 695 697 +2
Lines 214524 215823 +1299
Branches 41080 41293 +213
==========================================
+ Hits 192443 193844 +1401
+ Misses 14121 14097 -24
+ Partials 7960 7882 -78
🚀 New features to boost your workflow:
|
|
If this gets merged, #62600 should get reversed |
There was a problem hiding this comment.
#62553 (comment)
That can be a follow-up. It would reduce the UUID entropy so would probably need to be optional.
If this gets merged, #62600 should get reversed
In making this optional and documenting it it can also describe the nuance in the docs.
Why do you think it should be optional? |
First of all I don't think this is needed in the first place. Second, as @Renegade334's original comment that I've quoted says
|
lib/internal/crypto/random.js
Outdated
| validateObject(options, 'options'); | ||
| const { | ||
| disableEntropyCache = false, | ||
| monotonic = true, |
There was a problem hiding this comment.
| monotonic = true, | |
| monotonic = false, |
I agree and made it optional via an argument, but I don't agree it's not needed, there's a lot of use cases where a counter is critical. The most used UUID v7 library does this: https://github.com/LiosK/uuidv7/blob/main/src/index.ts#L263 |
Follow up #62553